public class CaseTriggerHelpers {
        
    public class Log4Case {
        string buf = ''; string indent = ''; List<String> indentSavepoints = new List<String>();
        public Log4Case add(string msg) { if (msg!=null) this.buf += (this.indent+'- '+msg+'\n'); return this;  }
        public Log4Case addError(string msg) { this.add('ERROR:  ' + msg); return this;  }
        public Log4Case addDecision(string decidedStr, string reasonStr) { this.add(decidedStr + ' b/c ' + reasonStr); return this;  }
        public string getAllLines() { return this.buf; } 
        public Log4Case incIndent() { indent += '  '; return this; }
        public Log4Case decIndent() { 
            integer newInlen = indent.length()-2; 
            this.indent = (newInlen>0) ? (indent.substring(0,newInlen)) : ''; return this; }
        public Log4Case clear() { buf = ''; indent = ''; return this; }
        public Log4Case writeOut(Case c) { 
            string str = this.getAllLines();
            try { 
                string subjStr = ' ' + ((c.Subject==null)?(''):(c.Subject));
                if (subjStr.contains('mwg')) c.Description += str.replace('  ','..'); 
            } catch(Exception e) { 
                add('NOTE:  Failed to write to Case description.');
            } finally {
                system.debug( '>>> L4C:\n' + getAllLines() ); 
            }
            return this; 
        }
        public Log4Case restoreIndent() { 
            integer maxIdx = indentSavepoints.size()-1;
            if (maxIdx>=0) { this.indent = indentSavepoints.remove(maxIdx); }
            return this;
        }
        public Log4Case saveIndent() { indentSavepoints.add(indent); return this; } 
        
    }
    public static testMethod void testLog4CaseIndentSaveRestore() {
        Log4Case testl4c = new Log4Case();
        testl4c.saveIndent();
        testl4c.saveIndent().incIndent().incIndent().incIndent();
        testl4c.add('a');
        testl4c.restoreIndent();
        testl4c.add('b');
        string strAll = testl4c.getAllLines();
        system.debug(strAll);
       
        // Try to break it
        Log4Case testl4c2 = new Log4Case();
        testl4c2.restoreIndent().restoreIndent().restoreIndent();
    }
    public static testMethod void testLog4Case() {
        Log4Case testl4c = new Log4Case();
        testl4c.add('a');
        testl4c.add('');
        string strAll = testl4c.getAllLines();
        system.assert(strAll != '');
        testl4c.add(null);
        system.assert(strAll != '');
        system.debug('>>>L4C>>> "' + testl4c.getAllLines() + '"' );
        
        Case c = new Case();
        testl4c.writeOut(null);
        testl4c.writeOut(c).clear();
        system.debug('>>>L4C>>> "' + c.Description + '"' );
        system.assert(c.Description == null);
        
        Case c2 = new Case(Subject='contains magic tooken:  mwg');

        testl4c.writeOut(null);
        testl4c.writeOut(c2).clear();
        system.debug('>>>L4C>>> "' + c2.Description + '"' );
        system.assert(c2.Description != null);
        
        Case c3 = new Case(Subject='no magic',Description='');
        testl4c.writeOut(null);
        testl4c.writeOut(c3).clear();
        system.debug('>>>L4C>>> "' + c3.Description + '"' );
        system.assert(c3.Description == null);
    }
    public static testMethod void testLog4CaseOneLiner() {
        Log4Case testl4c = new Log4Case();
        testl4c.incIndent().Add('x').decIndent();
        system.debug('>>>L4C>>> "' + testl4c.getAllLines() + '"' );
        system.assert( testl4c.getAllLines() != '');
    }
    public static testMethod void testLog4CaseIncDec() {
        Log4Case testl4c = new Log4Case();
        testl4c.incIndent().Add('x').decIndent().Add('y');
        system.debug('>>>L4C>>> "' + testl4c.getAllLines() + '"' );
        system.assert( testl4c.getAllLines() != '');
    }
    public static testMethod void testLog4CaseClear() {
        Log4Case testl4c = new Log4Case();
        system.assert(testl4c.Add('x').clear().getAllLines()=='');
        system.assert(testl4c.Add('x').getAllLines()!='');
    }
    public static testMethod void testLog4CaseAddError() {
        Log4Case l1 = new Log4Case();
        Log4Case l2 = new Log4Case();
        system.assert(l1.add('ERROR:  x').getAllLines()==l2.addError('x').getAllLines());
    } 
    public static testMethod void testLog4CaseAddDecision() {
        Log4Case l1 = new Log4Case();
        Log4Case l2 = new Log4Case();
        system.assert(l1.add('x b/c y').getAllLines()==l2.addDecision('x','y').getAllLines());
    }
    
    //
    // Custom Exceptions and Error Codes 4 Case 
    //
    // If calcs fail b/c a required field is missing, we throw one of these and catch
    // it in the trigger body, where we use it to mark the "Clock Status" field.
    //
    public static final integer ENT_WAS_NULL = 1;
    public static final integer CASE_WAS_NULL = 2;
    public static final integer ENT_HAS_NULL_OR_INACTIVE_BH = 3;
    public static final integer CASE_HAS_INACTIVE_BH = 4;
    public static final integer CASE_HAS_NULL_BH = 5;
    public static final integer CASE_PRIORITY_NOT_MAPPED_OR_MAPPING_INVALID = 6;
    public static final integer NO_RESTART_INFO = 7;
    public static final integer CANNOT_STOP_CLOCK_IF_EXPIRED = 8;

    public class E2CalcsBadInputException extends Exception {
        string msg;
        public E2CalcsBadInputException( integer problemCode ) {
            msg = String.valueOf(problemCode);
            System.debug('CAUGHT E2CalcsBadInputException:  error=' + problemCode + ' / ' + msg);
        }
        public string getErrVal() { return ' (' +  msg + ')'; }

    }
    
    public static testMethod void testE2CalcsBadInputException() {
        for(Integer i=0; i<10; i++) {
            try { throw new E2CalcsBadInputException(1); }
            catch( E2CalcsBadInputException e ) { system.debug(e.getErrVal()); }
        }
    }

}